package sicnu.cs.ich.common.services.impl;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.DefaultScriptExecutor;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import javax.validation.Valid;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @author Lenovo
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class RedisService {

    private final RedisTemplate<String, Object> redisTemplate;


    public boolean setEx(String key, long time) {
        try {
            redisTemplate.expire(key, time, TimeUnit.SECONDS);
            return true;
        } catch (Throwable e) {
            log.error("设置过期时间失败：key{}，time：{}s", key, time);
            return false;
        }
    }
    public boolean cacheSet(String k, Object v, long time){
        try {
            String key = k;
            var ops = redisTemplate.opsForSet();
            ops.add(k, v);
            if (time > 0) {
                redisTemplate.expire(key, time, TimeUnit.SECONDS);
            }
            return true;
        } catch (Throwable e) {
            log.error("缓存存入失败key:[{}] value:[{}]", k, v);
        }
        return false;
    }

    /**
     * 添加 key:string 缓存
     *
     * @param k    key
     * @param v    value
     * @param time time
     */
    public boolean cacheValue(String k, Object v, long time) {
        try {
            String key = k;
            ValueOperations<String, Object> ops = redisTemplate.opsForValue();
            ops.set(key, v);
            if (time > 0) {
                redisTemplate.expire(key, time, TimeUnit.SECONDS);
            }
            return true;
        } catch (Throwable e) {
            log.error("缓存存入失败key:[{}] value:[{}]", k, v);
        }
        return false;
    }


    /**
     * 添加 key:string 缓存
     *
     * @param key   key
     * @param value value
     */
    public boolean cacheValue(String key, Object value) {
        return cacheValue(key, value, -1);
    }


    public boolean containsKey(String key) {
        try {
            return redisTemplate.hasKey(key);
        } catch (Throwable e) {
            log.error("判断缓存存在失败key:[" + key + "],错误信息 ERROR[{}]", e);
        }
        return false;
    }

    /**
     * 根据 key 获取缓存value
     *
     * @param key key
     * @return value
     */
    public Object getValue(String key) {
        try {
            ValueOperations<String, Object> ops = redisTemplate.opsForValue();
            return ops.get(key);
        } catch (Throwable e) {
            log.error("根据 key 获取缓存失败，当前key:[{}],失败原因 Coder:[{}]", key, e);
        }
        return null;
    }

    /**
     * 缓存 list 集合
     *
     * @param k    key
     * @param v    value
     * @param time 时间
     */
    public boolean cacheList(String k, List<?> v, long time) {
        try {
            // 直接覆盖写
            redisTemplate.delete(k);
            var opsForList = redisTemplate.opsForList();
            opsForList.leftPushAll(k,v.toArray());

            if (time > 0) {
                redisTemplate.expire(k, time, TimeUnit.SECONDS);
            }
            return true;
        } catch (Throwable e) {
            log.error("缓存list失败 当前 key:[{}],失败原因 [{}]", k, e);
        }
        return false;
    }


    /**
     * 缓存 list
     *
     * @param k key
     * @param v value
     * @return true/false
     */
    public boolean cacheList(String k, List<?> v) {
        return cacheList(k, v, -1);
    }

    /**
     * 存入访问热度
     *
     * @param k key
     * @param v value
     */
    @Async
    public void putTypeHot(String k, Object v) {
        try {
            ListOperations<String, Object> opsForList = redisTemplate.opsForList();
            opsForList.leftPush(k, v);
            // 默认保存1000个访问热度
            opsForList.trim(k, 0, 100);
        } catch (Throwable e) {
            log.error("缓存list失败 当前 key:[{}],失败原因 [{}]", k, e);
        }
    }

    /**
     * 根据 key 获取 list 缓存
     *
     * @param k     key
     * @param start 开始
     * @param end   结束
     * @return 获取缓存区间内 所有value
     */
    public List<Object> getList(String k, long start, long end) {
        try {
            var opsForList = redisTemplate.opsForList();
            return opsForList.range(k, start, end);
        } catch (Throwable e) {
            log.error("获取list缓存失败 当前 key:[{}],失败原因 [{}]", k, e);
        }
        return null;
    }

    public List<Object> getList(String k, long start, long end, long cacheSecond) {
        try {
            ListOperations<String, Object> opsForList = redisTemplate.opsForList();
            if (cacheSecond > 0) {
                redisTemplate.expire(k, cacheSecond, TimeUnit.SECONDS);
            }
            return opsForList.range(k, start, end);
        } catch (Throwable e) {
            log.error("获取list缓存失败 当前 key:[{}],失败原因 [{}]", k, e);
        }
        return null;
    }


    /**
     * 根据 key 移除 list 缓存
     *
     * @param k key
     */
    public boolean removeOneOfList(String k) {
        try {
            ListOperations<String, Object> opsForList = redisTemplate.opsForList();
            opsForList.rightPop(k);
            return true;
        } catch (Throwable e) {
            log.error("移除list缓存失败 key[" + k + "], ERROR[" + e + "]");
        }
        return false;
    }

    public void removeAllValue() {
        Set<String> keys = redisTemplate.keys("*");
        assert keys != null;
        for (String key : keys) {
            redisTemplate.delete(key);
        }
    }

    public Set<String> allKeys() {
        return redisTemplate.keys("*");
    }

    public List<Object> allValues() {
        Set<String> keys = allKeys();
        ValueOperations<String, Object> ops = redisTemplate.opsForValue();
        List<Object> values = new ArrayList<>();
        for (String key : keys) {
            values.add(ops.get(key));
        }
        return values;
    }

    /**
     * 根据 key 移除 value 缓存
     *
     * @param key key
     */
    public boolean removeValue(String key) {
        return remove(key);
    }

    /**
     * 根据 key 移除 list 缓存
     *
     * @param key key
     * @return true/false
     */
    public boolean removeList(String key) {
        return remove(key);
    }

    /**
     * 移除缓存
     *
     * @param key key
     * @return boolean
     */
    private boolean remove(String key) {
        try {
            redisTemplate.delete(key);
            return true;
        } catch (Throwable e) {
            log.error("移除缓存失败 key:[{}] 失败原因 [{}]", key, e);
        }
        return false;
    }

    public Set<String> keys(String pattern) {
        return redisTemplate.keys(pattern);
    }

    /**
     * 使用zset做排行榜添加元素
     */
    public void zsetIncrease(String key, Object member, Integer delta) {
        try {
            //var rank = RedisRankEnum.IchRank;
            var zSet = redisTemplate.opsForZSet();
            //zSet.incrementScore(rank.getDescription(),ichId,1);
            zSet.incrementScore(key, member, delta);
            //rank.getCurHot().increment();
        } catch (Throwable e) {
            log.error("排行榜修改失败", e);
        }
    }

    public Set<ZSetOperations.TypedTuple<Object>> getZset(String key, int start, int end) {
        //返回集合内元素的排名，以及分数（从小到大）
        return redisTemplate.opsForZSet().rangeWithScores(key, start, end);
    }

    public void removeZset(String key, long start, long end) {
        var zset = redisTemplate.opsForZSet();
        zset.removeRange(key, start, end);
    }

    public void cacheZset(String key, Set<ZSetOperations.TypedTuple<Object>> members) {
        var zset = redisTemplate.opsForZSet();
        zset.add(key, members);
    }

    /**
     * 设置比特位为true
     *
     * @param key    键
     * @param offset 偏移量
     * @param value  值
     */
    public void setBit(String key, Integer offset, Boolean value) {
        RedisCallback<Boolean> call = connection ->
                connection.setBit(key.getBytes(StandardCharsets.UTF_8), offset, value);
        redisTemplate.execute(call);
    }

    /**
     * 获取比特位
     *
     * @param key    键
     * @param offset 偏移量
     */
    public Boolean getBit(String key, Integer offset) {
        RedisCallback<Boolean> call = connection ->
                connection.getBit(key.getBytes(StandardCharsets.UTF_8), offset);
        return redisTemplate.execute(call);
    }

    /**
     * 获取比特串中为1的个数
     *
     * @param key
     * @return
     */
    public Long getBitCount(String key) {
        RedisCallback<Long> call = connection ->
                connection.bitCount(key.getBytes(StandardCharsets.UTF_8));
        return redisTemplate.execute(call);
    }

    //用于存储标识此线程的ID
    private final ThreadLocal<String> threadIdBox = new ThreadLocal<>();

    /**
     * 加锁
     *
     * @param serviceId KEY  用于标识高并发修改资源的业务
     * @param timeout   超时时间
     * @param timeUnit  超时单位
     * @return 创建KEY成功返回true   创建KEY失败返回false
     */
    public boolean lock(String serviceId, Long timeout, TimeUnit timeUnit) {
        try {
            threadIdBox.set(UUID.randomUUID().toString());
            //如果KEY不存在，则创建KEY
            //KEY不存在，返回true  存在返回false
            return redisTemplate.opsForValue().setIfAbsent(serviceId, threadIdBox.get(), timeout, timeUnit);
        } catch (Exception e) {
            //引发任何异常，spring将不会创建KEY，直接返回false
            threadIdBox.remove();
            return false;
        }
    }

    /**
     * 释放锁
     *
     * @param serviceId KEY  用于标识高并发修改资源的业务
     */
    public boolean unlock(String serviceId) {
        //当前线程没有生成线程标识，说明压根没有创建过KEY
        if (threadIdBox.get() == null) return true;
        try {
            //lua脚本(保证查询和删除的原子性)
            //如果根据serviceId查找出的值与当前线程的标识码一致，则删除该KEY并返回删除结果，没找到KEY直接返回false
            String luaScript = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
            DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript<>(luaScript);
            redisScript.setResultType(Boolean.class);
            DefaultScriptExecutor defaultScriptExecutor = new DefaultScriptExecutor(redisTemplate);
            return (boolean) defaultScriptExecutor.execute(redisScript, Collections.singletonList(serviceId), threadIdBox.get());
        } catch (Exception e) {
            //引发任何异常，spring将不会执行删除操作，直接返回false
            return false;
        } finally {
            threadIdBox.remove();
        }
    }


}

